
Alkaline.InputSource = function(stdOut, stdErr, stdWarn) {
	this.stdOut = stdOut; this.stdErr = stdErr; this.stdWarn = stdWarn;
	
	this.files = [];
	this.input = '';
	
	this.sourceNameHash = {};
}

Alkaline.InputSource.RX = {
	dir: /^((?:.+\/)[^\/\.]+)(?:$|\?.*)/,
	html: /^(.+\.html)(?:$|\?.*)/,
	js: /^(.+\.js)(?:$|\?.*)/,
	ng: /\n/g,
	scriptStartg: /<script([^>]*)(\/?>)/g
}

Alkaline.InputSource.prototype.addFileInfo = function(filename, startLine, startChar, content) {
	if (!this.sourceNameHash[filename])
		this.sourceNameHash[filename] = 1;
	else
		filename += ' #' + ++this.sourceNameHash[filename];
	var prevLines = this.files.length ? this.files[this.files.length-1].endLine : 0;
	startLine += 1;
	content += '\n';
	var lines = content.match(Alkaline.InputSource.RX.ng).length;
	this.files.push({
		startLine: startLine,	// completely unrelated to endLine/prevLines
		prevLines: prevLines,
		endLine: prevLines + lines,
		filename: filename,
		startChar: startChar + 1
	});
	this.input += content;
}

Alkaline.InputSource.prototype.addCode = function(code, blockName) {
	if (!blockName) blockName = 'Dynamic code block';
	this.addFileInfo(blockName, 0, 0, code);
}

Alkaline.InputSource.prototype.addHTMLFile = function(filename) {
	var fileDir = filename.lastIndexOf('/') > 0 ? filename.substr(0, filename.lastIndexOf('/')+1) : '';
	var content = readFile(filename);
	var contentPos = 0;
	var m, startLine = 0, startChar = 0;
	
	var movePos = function(newPosition) {
		if (newPosition < contentPos)
			throw new Error('bad call');
		var passed = content.substring(contentPos, newPosition);
		contentPos = newPosition;
		// update startLine, startChar
		var lastN = passed.lastIndexOf('\n');
		if (lastN >= 0) {
			startLine += passed.match(Alkaline.InputSource.RX.ng).length;
			startChar = passed.length-1 - lastN;
		} else
			startChar += passed.length;
		
	}
	
	var delayed = [];
	while (m = Alkaline.InputSource.RX.scriptStartg.exec(content)) {
		movePos(m.index + m[0].length);
		var scriptContent = '';
		var scriptEnd = content.indexOf('</script>', contentPos);
		if (scriptEnd < 0)
			this.stdWarn('Expected "</script>" but not found in file ' + filename + ' at line ' + startLine + ', character ' + startChar + '!');
		else
			scriptContent = content.substring(contentPos, scriptEnd);
		
		var defer = /\sdefer/.test(m[1].replace(/="[^"]"/g, ''));
		
		var m1 = m[1].match(/\ssrc="([^"]+)"/);
		if (m1) {
			var src = fileDir + m1[1];
			if (defer)
				delayed.push(src);
			else
				this.addFileInfo(src, 0, 0, readFile(src));
		} else
			this.addFileInfo(filename, startLine, startChar, scriptContent);
		
		if (scriptEnd >= 0)
			movePos(scriptEnd);
	}
	
	for (var i = 0; i < delayed.length; i++)
		this.addFileInfo(delayed[i], 0, 0, readFile(delayed[i]));
	
	var onload = content.match(/(<body[^>]+onload=")([^"]+)"/);
	if (onload) {
		var passed = content.substr(0, onload.index + onload[1].length + 1);
		var startChar = passed.lastIndexOf('\n');
		if (startChar > -1) {
			startChar = passed.length - 2 - startChar;
			startLine = passed.match(Alkaline.InputSource.RX.ng).length;
		} else {
			startChar = passed.length - 1;
			startLine = 0;
		}
		
		this.addFileInfo(filename, startLine, startChar, onload[2]);
	}
}

Alkaline.InputSource.prototype.addFilesInDir = function(dir) {
	if (dir.charAt(dir.length-1) != '/')
		dir += '/';
	var list = listFiles(dir, 'js');
	for (var i = 0; i < list.length; i++)
		this.addFile(dir + list[i]);
	var list = listFiles(dir, 'html');
	for (var i = 0; i < list.length; i++)
		this.addFile(dir + list[i]);
}

Alkaline.InputSource.prototype.addFile = function(filename) {
	var m;
	if (m = filename.match(Alkaline.InputSource.RX.js))
		this.addFileInfo(m[1], 0, 0, readFile(m[1]));
	else if (m = filename.match(Alkaline.InputSource.RX.html))
		this.addHTMLFile(m[1]);
	else if (m = filename.match(Alkaline.InputSource.RX.dir))
		this.addFilesInDir(m[1]);
	else
		this.stdErr('Unknown file type: "' + filename + '"');
}

// add 0 or more files using wildcards
Alkaline.InputSource.prototype.addSource = function(filename) {
	if (filename.indexOf('*') < 0 && filename.indexOf('?') < 0) {
		// no wildcard is present
		this.addFile(filename);
		return;
	}
	var dir = '.';
	if (filename.indexOf('/') > -1) {
		dir = filename.substr(0, filename.lastIndexOf('/'));
		filename = filename.substr(dir.length+1);
	}
	filename = filename.replace(/[^\w]/g, function(chr) {
		return chr == '*' ? '.*' :
			chr == '?' ? '.' :
			'\\' + chr;	// escape
	});
	filename = new RegExp('^' + filename + '$');
	var files = listFiles(dir);
	dir = dir === '.' ? '' : (dir + '/');
	for (var i = 0; i < files.length; i++)
		if (filename.test(files[i]))
			this.addFile(dir + files[i]);
}

Alkaline.InputSource.prototype.getLocation = function(line, pos) {
	var filename = 'unknown file';
	for (var i = 0; i < this.files.length; i++)
		if (this.files[i].prevLines <= line && this.files[i].endLine >= line) {
			if (line == this.files[i].prevLines+1)
				pos += this.files[i].startChar-1;
			filename = this.files[i].filename;
			line = line - this.files[i].prevLines - 1 + this.files[i].startLine;
			break;
		}
	return 'line ' + line + ', character ' + pos + ' in ' + filename;
}
